import time
import math
from vrpsc.Util.generate_init_solution import generate_solution_greedy
from vrpsc.Util.load_data import *
import numpy as np
from vrpsc.Util.operators import *
from vrpsc.Util.tensorUtil import parse_vehicle_routes


# from Util.tensorUtil import parse_vehicle_routes

"""
算法随想
1. 
greedy 方法对于大实例的exploitation有帮助
但是对于小规模的实例，random对于求得全局最优解很重要，因此必须加入random的部分
2.
贪婪得初始解生成方法画地为牢，可能导致小规模实例难以求得最优解
3.
增大温度T，也会提高算法求解到小规模最优解的可能性
"""

destructOperatorList = [destroy_couple_random,
                        destroy_couple_worst_cost,
                        destroy_couple_worst_distance,
                        destroy_couple_shaw]
constructOperatorList = [repair_couple_greedy,
                         repair_couple_greedy_urgency_priority,
                         repair_couple_greedy_cost_priority]

d_operator_num = len(destructOperatorList)  # 破坏算子个数
c_operator_num = len(constructOperatorList)  # 重建算子个数
wDestruct = [1 for _ in range(d_operator_num)]  # 破坏算子初始权重
wConstruct = [1 for _ in range(c_operator_num)]  # 重建算子初始权重


def destruct_construct(current_solution, d_num):
    destruct_index = np.random.choice(np.arange(len(wDestruct)), p=np.array(wDestruct) / sum(wDestruct))
    construct_index = np.random.choice(np.arange(len(wConstruct)), p=np.array(wConstruct) / sum(wConstruct))
    destroyed_info = destructOperatorList[destruct_index](current_solution, d_num)
    new_solution = constructOperatorList[construct_index](*destroyed_info, current_solution)
    return new_solution, destruct_index, construct_index



def ALNS(instance_name, temp_instance = None):
    if temp_instance is None:
        instance = read_excel(instance_name)
    else:
        instance = temp_instance
    timesDestruct = [0 for _ in range(d_operator_num)]       # 使用次数
    timesConstruct = [0 for _ in range(c_operator_num)]
    totalScoreDestruct = [0 for _ in range(d_operator_num)]  # 总的分数
    totalScoreConstruct = [0 for _ in range(c_operator_num)]
    wDestruct = [1 for _ in range(d_operator_num)]           # 破坏算子初始权重
    wConstruct = [1 for _ in range(c_operator_num)]          # 重建算子初始权重

    solution_table = {}  # 存hashkey对应的solution

    task_num = len(instance)
    d_num = math.ceil(task_num * ALNSConfig.d_num_coefficient)  # 破坏个数

    init_start = time.time()
    solution = generate_solution_greedy(instance)
    init_solution = solution
    init_end = time.time()
    # print(f"初始解生成时间: {init_end - init_start}")


    solution_table[solution.hash_key] = solution

    current_fitness = solution.get_fitness()
    # print(f"初始解的fitness:{current_fitness}")
    best_solution = solution
    best_fitness = current_fitness
    count = 0

    CONSTANT_T = get_T(instance)
    # print(f"温度:{CONSTANT_T}")

    duration = task_num ** 2 * (Config.M_child + Config.M_parent) * ALNSConfig.C / 5000
    # print(f"运行duration:{duration}")

    start_t = time.time()

    # while time.time() - start_t < duration:
    while count < 100:
        count += 1
        new_solution, destruct_index, construct_index = destruct_construct(solution, d_num)

        is_accept = False

        is_new = False  # 是否是一个之前为探索过的解
        if new_solution.hash_key not in solution_table.keys():
            solution_table[new_solution.hash_key] = new_solution
            is_new = True

        new_fitness = new_solution.get_fitness()

        scoreDestruct = 0
        scoreConstruct = 0

        if new_fitness < current_fitness:
            is_accept = True
            p_a = 1.0
            solution = new_solution
            current_fitness = new_fitness
            if new_fitness < best_fitness:
                best_solution = new_solution
                best_fitness = new_fitness
                scoreDestruct = ALNSConfig.sigma_1
                scoreConstruct = ALNSConfig.sigma_1
            else:
                if is_new:
                    scoreDestruct = ALNSConfig.sigma_2
                    scoreConstruct = ALNSConfig.sigma_2
        elif new_fitness == current_fitness:
            is_accept = True
            if is_new:
                scoreDestruct = ALNSConfig.sigma_2
                scoreConstruct = ALNSConfig.sigma_2
            p_a = 0
        else:
            p_a = math.exp((current_fitness - new_fitness) / CONSTANT_T)
            if random.random() < p_a:
                is_accept = True
                solution = new_solution
                current_fitness = new_fitness
                if is_new:
                    scoreDestruct = ALNSConfig.sigma_3
                    scoreConstruct = ALNSConfig.sigma_3

        timesDestruct[destruct_index] += 1
        timesConstruct[construct_index] += 1
        totalScoreDestruct[destruct_index] += scoreDestruct
        totalScoreConstruct[construct_index] += scoreConstruct

        if count % ALNSConfig.l_s == 0:
            for i in range(d_operator_num):
                if timesDestruct[i] != 0:
                    dTime = timesDestruct[i]
                else:
                    dTime = 1

                wDestruct[i] = wDestruct[i] * (1 - ALNSConfig.rho) + ALNSConfig.rho * totalScoreDestruct[i] / dTime
                totalScoreDestruct[i] = 0
                timesDestruct[i] = 0
            for i in range(c_operator_num):
                if timesConstruct[i] != 0:
                    cTime = timesConstruct[i]
                else:
                    cTime = 1
                wConstruct[i] = wConstruct[i] * (1 - ALNSConfig.rho) + ALNSConfig.rho * totalScoreConstruct[i] / cTime
                totalScoreConstruct[i] = 0
                timesConstruct[i] = 0
        # print(f"第{count}次迭代，最优fitness为：{round(best_fitness, 4)} 当前fitness为 ：{round(current_fitness, 4)} 新fitness：{round(new_fitness, 4)} d_index:{destruct_index} c_index:{construct_index} p: {round(p_a, 3)} is_accept:{is_accept} time:{round(time.time() - start_t, 4)}")
    print(f"best fitness:{best_fitness}  best solution:{best_solution}")
    # print(parse_vehicle_routes(best_solution.get_code()))
    return init_solution, best_solution


def _calculate_and_package_metrics(costs, dist_costs, delay_costs, avg_runtime_per_rep):
    """
    Calculates statistics and packages data for a given set of solution metrics.

    Args:
        costs (list): List of total costs (fitness) for each run.
        dist_costs (list): List of distance costs for each run.
        delay_costs (list): List of delay costs (tardiness) for each run.
        avg_runtime_per_rep (float): Average runtime per repetition.

    Returns:
        list: A data package containing calculated statistics.
    """
    costs_np = np.array(costs)
    delay_costs_np = np.array(delay_costs)
    dist_costs_np = np.array(dist_costs)

    min_cost_delay = delay_costs_np.min()
    min_cost_dist = dist_costs_np.min()
    mean_cost_delay = delay_costs_np.mean()
    mean_cost_dist = dist_costs_np.mean()
    mean_cost = costs_np.mean()
    overall_best_cost = costs_np.min() # The overall best cost found across all runs

    data_package = [
        None,  # Placeholder (e.g., for best solution code, if needed in package)
        dist_costs,
        delay_costs,
        avg_runtime_per_rep,
        min_cost_delay,
        min_cost_dist,
        mean_cost_delay,
        mean_cost_dist,
        mean_cost,
        overall_best_cost,
    ]
    return data_package


def run_alns_experiment(instance_name, repeated):
    runtime_start_total = time.time()

    # Lists to store metrics for ALNS solutions
    alns_costs = []
    alns_delay_costs = []
    alns_dist_costs = []

    # Lists to store metrics for initial greedy solutions
    greedy_costs = []
    greedy_delay_costs = []
    greedy_dist_costs = []

    best_alns_cost = float('inf')  # Initialize with a very large number
    best_alns_solution_code = None  # Stores the actual code/representation of the best ALNS solution

    print(f"Starting ALNS for instance: {instance_name} with {repeated} repetitions...")

    for i in range(repeated):
        print(f"--- Repetition {i + 1}/{repeated} ALNS ---")

        # Execute the ALNS algorithm for the current repetition
        init_solution, alns_solution = ALNS(instance_name)

        # Process ALNS solution metrics
        alns_costs.append(alns_solution.get_fitness())
        alns_dist_costs.append(alns_solution.distance)
        alns_delay_costs.append(alns_solution.tardiness)

        # Process initial greedy solution metrics
        greedy_costs.append(init_solution.get_fitness())
        greedy_dist_costs.append(init_solution.distance)
        greedy_delay_costs.append(init_solution.tardiness)

        # Update best_alns_cost and best_alns_solution_code if the current ALNS solution is better
        if alns_solution.get_fitness() < best_alns_cost:
            best_alns_cost = alns_solution.get_fitness()
            best_alns_solution_code = alns_solution.get_code()  # Store the code of the best ALNS solution found so far

    total_runtime = time.time() - runtime_start_total
    avg_runtime_per_repetition = total_runtime / repeated

    # Calculate and package metrics for ALNS solutions
    alns_data_package = _calculate_and_package_metrics(
        alns_costs,
        alns_dist_costs,
        alns_delay_costs,
        avg_runtime_per_repetition
    )
    # Update the first element of alns_data_package with the best solution code
    alns_data_package[0] = best_alns_solution_code

    # Calculate and package metrics for initial greedy solutions
    greedy_data_package = _calculate_and_package_metrics(
        greedy_costs,
        greedy_dist_costs,
        greedy_delay_costs,
        avg_runtime_per_repetition # Assuming greedy solution runtime is part of the total ALNS runtime
    )
    greedy_data_package[0] = init_solution.get_code()  # Store the code of the initial greedy solution
    return best_alns_solution_code, alns_data_package, greedy_data_package




# if __name__ == "__main__":
#     instance_name = "tasks_50_5"
#     solution = ALNS(instance_name)
#     print(solution.get_code())
#     print(f"distance:{solution.distance}  tardiness:{solution.tardiness}")
#     print(parse_vehicle_routes(solution.get_code()))
#     pass
